-module(db).
-export([init/0, insert_player/2, get_player/1, get_players/0, get_party_size/0, save_player/1, remove_player/1]).
-export([get_dungeon/0, get_monster/1, get_monsters/0, save_monster/1, kill_monster/1, create_dungeon/0]).

-include_lib("stdlib/include/qlc.hrl").
-include("db.hrl").

%% Sets up the db schema
init() ->
  mnesia:create_table(player,
                      [{attributes, record_info(fields, player)}]),
  mnesia:create_table(monster,
                      [{attributes, record_info(fields, monster)}]),
  mnesia:create_table(player_item,
                      [{type, bag},
                       {attributes, record_info(fields, player_item)}]),
  mnesia:create_table(dungeon,
                      [{attributes, record_info(fields, dungeon)}]),
  mnesia:create_table(dungeon_item,
                      [{type, bag},
                       {attributes, record_info(fields, dungeon_item)}]).

%%
%% Players
%%

%% Insert a new player into the db with name
insert_player(Name, Class) ->
  {A1,A2,A3} = now(),
  random:seed(A1, A2, A3),
  Trans = fun() ->
    {_, [Dungeon]} = db:get_dungeon(),
    {Map} = Dungeon#dungeon.json,
    {Spawn} = proplists:get_value(<<"spawn">>, Map),
    {MainSpawn} = proplists:get_value(<<"main">>, Spawn),
    X = proplists:get_value(<<"x">>, MainSpawn),
    Y = proplists:get_value(<<"y">>, MainSpawn),
    
    %% Create the player. Here the base stats are also rolled and are in the
    %% range 12 to 28 inclusive (and multiplied by 10 for durability)
    Durability = (random:uniform(17) + 11) * 10,
    mnesia:write(#player{name = Name,
                         class = Class,
                         damage = random:uniform(17) + 11,
                         dexterity = random:uniform(17) + 11,
                         dodge = random:uniform(17) + 11,
                         durability = Durability,
                         max_durability = Durability,
                         x = X,
                         y = Y})
  end,
  mnesia:transaction(Trans).

%% Update a player record
save_player(Player) ->
  Trans = fun() ->
    mnesia:delete(player, Player#player.name, write),
    mnesia:write(Player)
  end,
  mnesia:transaction(Trans).

%% Fetch a player from the database
get_player(Name) ->
  Trans = fun() ->
    mnesia:read(player, Name)
  end,
  mnesia:transaction(Trans).

%% Fetch a list of connected players
get_players() ->
  ProcessPlayer = fun(Player, NewAcc) ->
    PropList = {[{<<"name">>, Player#player.name},
                 {<<"class">>, Player#player.class},
                 {<<"x">>, Player#player.x},
                 {<<"y">>, Player#player.y}]},
    [PropList|NewAcc]
  end,
  Trans = fun() ->
    mnesia:foldl(ProcessPlayer, [], player)
  end,
  mnesia:transaction(Trans).

%% Get the number of players in the party
get_party_size() ->
  mnesia:table_info(player, size).

%% Remove a player with the given name
remove_player(Name) ->
  Trans = fun() ->
    mnesia:delete(player, Name, write)
  end,
  mnesia:transaction(Trans).

%%
%% Dungeons
%%

%% Unix script I/O helpers
response_reader(Port) ->
  response_reader_loop(Port, []).

response_reader_loop(Port, L) ->
  receive
      {Port, {data, Bin}} ->
        %%io:format("Received: ~p~n", [Bin]),
        response_reader_loop(Port, [Bin|L]);
      {Port, eof} ->
        port_close(Port),
        {ok, lists:reverse(L)};
      Other ->
        io:format("Unexpected: ~p~n", [Other]),
        response_reader_loop(Port, L)
  end.

%% Create a new dungeon
create_dungeon() ->
  GetFirst = fun() ->
    mnesia:first(dungeon)
  end,
  Remove = fun(Level) ->
    mnesia:delete(dungeon, Level, write)
  end,
  Create = fun(Level) ->
    Port = open_port({spawn, "python mapgen.py"}, [binary, use_stdio, eof]),
    {ok, Result} = response_reader(Port),
    {JSONData} = ejson:decode(Result),
    SpawnMonsters = fun(Monsters) ->
      lists:map(fun(Position) ->
                  mnesia:write(#monster {position = Position,
                                         type = <<"blob">>,
                                         damage = 0,
                                         dexterity = 0,
                                         dodge = 0,
                                         durability = 0,
                                         level = 0,
                                         dungeon_level = Level})
                end,
                Monsters)
    end,
    %%io:format("~p", JSONData),
    %%mnesia:transaction(SpawnMonsters, proplists:get_value(<<"monsters">>, JSONData)),
    SpawnMonsters(proplists:get_value(<<"monsters">>, JSONData)),
    MapJSON = {proplists:delete(<<"monsters">>, JSONData)},
    if
      Level > 1 -> Remove(Level - 1);
      true -> ok
    end,
    mnesia:write(#dungeon {dungeon_level = Level,
                           json = MapJSON})
  end,
  {_, LastLevel} = mnesia:transaction(GetFirst),
  if
    LastLevel == '$end_of_table' -> Level = 1;
    true -> Level = LastLevel + 1
  end,
  mnesia:transaction(Create, [Level]).

%% Fetch a dungeon by key
get_dungeon(Key) ->
  Trans = fun() ->
    mnesia:read(dungeon, Key)
  end,
  mnesia:transaction(Trans).

%% Fetch the current dungeon from the database, or create a new one if none exists
get_dungeon() ->
  Trans = fun() ->
    mnesia:first(dungeon)
  end,
  {_, Key} = mnesia:transaction(Trans),
  %%io:format("Result: ~p~n", [Key]),
  if    %% Check if a dungeon exists
    Key == '$end_of_table' ->
      create_dungeon(), %% Make a new one
      {_, NewKey} = mnesia:transaction(Trans),
      get_dungeon(NewKey);
    true ->
      get_dungeon(Key)  %% Return existing one
  end.

%%
%% Monsters
%%

%% Fetch a list of monsters in the dungeon
get_monsters() ->
  ProcessMonster = fun(Monster, NewAcc) ->
    [X, Y] = Monster#monster.position,
    PropList = {[{<<"type">>, Monster#monster.type},
                 {<<"x">>, X},
                 {<<"y">>, Y}]},
    [PropList|NewAcc]
  end,
  Trans = fun() ->
    mnesia:foldl(ProcessMonster, [], monster)
  end,
  mnesia:transaction(Trans).

%% Fetch a monster by key (position tuple)
get_monster(Key) ->
  Trans = fun() ->
    mnesia:read(monster, Key)
  end,
  mnesia:transaction(Trans).

%% Update a monster record
save_monster(Monster) ->
  Trans = fun() ->
    mnesia:delete(monster, Monster#monster.position, write),
    mnesia:write(Monster)
  end,
  mnesia:transaction(Trans).

%% Kill the monster with the given position and award party experience
kill_monster(Position) ->
  {_, [Monster]} = get_monster(Position),
  GiveExp = fun(Player, NewAcc) ->
    mnesia:delete(player, Player#player.name, write),
    mnesia:write(Player#player{xp=Player#player.xp + Monster#monster.level * 300}),
    [Player|NewAcc]
  end,
  Trans = fun() ->
    mnesia:foldl(GiveExp, [], player),
    mnesia:delete(monster, Position, write)
  end,
  mnesia:transaction(Trans).